﻿// word_index.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include<bits/stdc++.h>
#include<dirent.h>
#include<ctime>
#include<unordered_map>
#include<iostream>

using namespace std;

#define INF 0x3f3f3f3f
#define LINE_MAX_SIZE 1024

const char *ERROR1 = "参数缺失";
const char *ERROR2 = "参数格式不正确";
const char *ERROR3 = "参数不存在";
const char *ERROR4 = "文件打开失败";
const char *ERROR5 = "缺少文件路径";

const char *paramter_list[] = { "f","d","n","x","p","v","s","a","h" };
char buff[1000];

vector<pair<string, int> > vec;
unordered_map<string, int> kw;
unordered_map<string, string> dic;

void help() {
	std::cout << "---------------help---------------" << "\n";
	std::cout << "1. -f参数:" << "\n";
	std::cout << "    说明: 指定文件路径" << "\n";
	std::cout << "    示例: wf -f 'word.txt'" << "\n";
	std::cout << "2. -d参数:" << "\n";
	std::cout << "    说明: 指定文件夹路径" << "\n";
	std::cout << "    示例: wf -d './data/'" << "\n";
	std::cout << "3. -s参数:" << "\n";
	std::cout << "    说明: 指定是否递归搜索文件夹" << "\n";
	std::cout << "4. -n参数:" << "\n";
	std::cout << "    说明: 输出前n个单词" << "\n";
	cout << "    示例: wf -n 3" << "\n";
	cout << "5. -x参数:" << "\n";
	cout << "    说明: 指定停词文件路径" << "\n";
	cout << "    示例: wf -x 'stopword.txt'" << "\n";
	cout << "6. -p参数:" << "\n";
	cout << "    说明: 指定统计短语的单词数目" << "\n";
	cout << "    示例: wf -p 3" << "\n";
	cout << "7. -a参数:" << "\n";
	cout << "    说明: 将文件目录下的所有文件一起统计" << "\n";
	cout << "    示例: wf -a" << "\n";
	cout << "8. -h参数:" << "\n";
	cout << "    说明: 查看所有指令用法" << "\n";
	cout << "    示例: wf -h/--help" << "\n";
	cout << "----------------------------------" << "\n";
}
bool cmp(const pair<string, int> &p1, const pair<string, int> &p2) {
	return p1.second == p2.second ? p1.first < p2.first : p1.second > p2.second;
}
void AdjustDown(vector<pair<string, int> > &a, size_t root, size_t size)//下调
{//小堆
	size_t parent = root;
	size_t child = parent * 2 + 1;
	while (child < size)
	{
		if (child + 1 < size && a[child].second > a[child + 1].second)
		{
			++child;
		}
		if (a[parent].second > a[child].second)
		{
			swap(a[parent], a[child]);
			parent = child;
			child = parent * 2 + 1;
		}
		else//注意不满足交换条件时跳出本次循环
		{
			break;
		}
	}
}
void GetTopk(const vector<pair<string, int> >& a, int n, int k)//N个数中找最大的前k个数--小堆实现
{
	assert(n > k);
	vector<pair<string, int> > TopkArray;
	for (size_t i = 0; i < k; i++)
	{
		TopkArray.push_back(a[i]);
	}
	for (int i = (k - 2) / 2; i >= 0; --i)//建小堆
	{
		AdjustDown(TopkArray, i, k);
	}
	//从第k个元素开始到第n个元素分别与堆顶元素进行比较，较大数据入堆顶，再对整个堆进行下调，使堆顶存放最小元素（小堆）
	for (size_t i = k; i < n; ++i)
	{
		if (vec[i].second > TopkArray[0].second)
		{
			TopkArray[0].first = a[i].first;
			TopkArray[0].second = a[i].second;
			AdjustDown(TopkArray, 0, k);
		}
	}
}
/*
 *数字转换函数
 *@param: str 源字符串
 *@return: d  转换后的数字
 */
int to_digit(char *str) {
	int d = 0;
	for (int i = 0; i < strlen(str); i++) {
		if (str[i] >= '0' && str[i] <= '9') {
			d *= 10;
			d += str[i] - '0';
		}
		else {
			return -1;
		}
	}
	return d;
}

/*
 *归一化函数
 *@param: verb 单词字符串
 *@return: void
 */
void normalizate(string &verb) {
	for (int i = 0; i < verb.size(); i++) {
		if (verb[i] >= 'A' && verb[i] <= 'Z') {
			verb[i] += (int)('a' - 'A');
		}
	}
	if (dic.find(verb) != dic.end()) {
		verb = dic[verb];
	}
}
/*
 *分割词组函数
 *@param: phrase 初始短语集合
 *@param: ls     分割后的短语集合
 *@param: num    每个短语的长度
 *@reutrn: void
 */
void split_phrase(vector<string> &phrase, vector<string> &ls, int num) {
	ls.clear();
	if (phrase.size() < num) return;
	string tmp;
	for (int i = 0; i <= phrase.size() - num; i++) {
		for (int j = 0; j < num - 1; j++) {
			tmp += phrase[i + j];
			tmp += " ";
		}
		tmp += phrase[i + num - 1];
		ls.push_back(tmp);
		tmp.clear();
	}
}
/*
 *判断是否在停词表中
 *@param: word         待判断的单词
 *@param: stopwordList 停词表
 *return: 在停词表中返回true,否则返回false
 */
bool isStopWord(string word, unordered_map<string, bool> &stopwordList) {
	if (stopwordList.size() == 0)	return false;
	if (stopwordList.find(word) != stopwordList.end()) {
		return true;
	}
	return false;
}
/*
 *读取停词表
 *@param: filePath 停词表的文件路径
 *@param: stopword 存储停词表的容器
 *@return: 读取成功返回true,否则返回false
 */
bool readStopWordFile(string filePath, unordered_map<string, bool> &stopword) {
	if (filePath.size() == 0)	return false;
	FILE *fp = fopen(filePath.c_str(), "r");
	if (fp == NULL) {
		perror(ERROR3);
		return false;
	}
	while (~fscanf(fp, "%s", buff)) {
		stopword.insert(make_pair(buff, true));
	}
	return true;
}
/*
 *读取动词变化表
 *@param: filePath 动词变化表的文件路径
 *@return: 读取成功返回true,否则返回false
 */
bool readVerbList(string filePath) {
	if (filePath.size() == 0)	return false;
	FILE *fp = fopen(filePath.c_str(), "r");
	if (fp == NULL) {
		cout << ERROR3 << "\n";
		return false;
	}
	while (fgets(buff, LINE_MAX_SIZE, fp) != NULL) {
		stringstream ss(buff);
		string base_verb, other_verb;
		ss >> base_verb;
		while (ss >> other_verb) {
			dic[other_verb] = base_verb;
		}
	}
	return true;

}
/*
 *读取文件夹下所有文件
 *@param: basePath 文件/文件夹路径
 *@param: fileList 存储文件名称的列表
 *@param: flg      是否递归子文件夹
 *@return: 读取成功返回true,否则返回false
 */
bool readFileList(const char *basePath, vector<string> &fileList, bool flg) {
	DIR *dir;
	struct dirent *ptr;
	char base[1000];

	if ((dir = opendir(basePath)) == NULL) {
		perror("Open dir error...");
		return false;
	}
	while ((ptr = readdir(dir)) != NULL) {
		if (strcmp(ptr->d_name, ".") == 0 || strcmp(ptr->d_name, "..") == 0)    ///current dir OR parrent dir
			continue;
		else if (ptr->d_type == 8)  //file
			fileList.push_back(string(ptr->d_name));
		else if (ptr->d_type == 10) //link file
			fileList.push_back(string(ptr->d_name));
		else if (ptr->d_type == 4 && flg) { //dir
			memset(base, '\0', sizeof(base));
			strcpy(base, basePath);
			strcat(base, "/");
			strcat(base, ptr->d_name);
			readFileList(base, fileList, flg);
		}
	}
	closedir(dir);
	return true;
}

/*
 *处理及输出指令结果
 *@param: path         文件/文件夹路径
 *@param: stopfilePath 停词表文件路径
 *@param: verbfilePath 动词变化表路径
 *@param: rankNum      输出前n个结果
 *@param: pharseNum    短语单词个数
 *@param: isdigui      是否递归子文件夹
 *@param: isall        是否将所有文件一起统计
 *@param: isdir        是否是文件夹路径
 *@return: 操作成功返回true,否者返回false
 */
bool sol(string path, string stopfilePath, string verbfilePath,
	int rankNum, int phraseNum, bool isdigui, bool isall, bool isdir) {

	unordered_map<string, bool> stopwordList;
	vector<string> fileList, phrase, ls;

	if (isdir) {
		readFileList(path.c_str(), fileList, isdigui);
	}
	else {
		fileList.push_back("");
	}

	readStopWordFile(stopfilePath, stopwordList);
	readVerbList(verbfilePath);

	for (int f = 0; f < fileList.size(); f++) {
		string filepath = string(path);
		filepath.append(fileList[f]);
		if (!isall)
			printf("%s\n", filepath.c_str());
		//			cout << filepath << "\n";
		FILE *fp = fopen(filepath.c_str(), "r");
		if (fp == NULL) {
			cout << ERROR4 << "\n";
			return false;
		}
		while (~fscanf(fp, "%s", buff)) {
			int len = strlen(buff);
			if (isalpha(int(buff[len - 1])) && !isStopWord(buff, stopwordList)) {
				phrase.push_back(buff);
				continue;
			}
			buff[len - 1] = '\0';
			if (len > 1 && !isStopWord(buff, stopwordList)) {
				phrase.push_back(buff);
			}
			split_phrase(phrase, ls, phraseNum);
			for (int i = 0; i < ls.size(); i++) {
				// 归一化
				//	normalizate(ls[i]);
				kw[ls[i]]++;
			}
			phrase.clear();
		}
		if (phrase.size()) {
			split_phrase(phrase, ls, phraseNum);
			for (int i = 0; i < ls.size(); i++) {
				//normalizate(ls[i]);
				kw[ls[i]]++;
			}
			phrase.clear();
		}
		if ((isall && f == fileList.size() - 1) || !isall) {
			for (unordered_map<string, int>::iterator it = kw.begin(); it != kw.end(); it++) {
				vec.push_back(*it);
			}
			if (rankNum != INF && rankNum < vec.size()) {
				GetTopk(vec, vec.size(), rankNum);
				//	for(int i = 0; i < rankNum; i++) {
				//		unordered_map<string,int>::iterator tmpKW = kw.begin();
				//		for(unordered_map<string,int>::iterator it = kw.begin(); it != kw.end(); it++) {
				//			if(tmpKW->second < it->second || 
				//					(tmpKW->second == it->second && tmpKW->first > it->first) ) {
				//				tmpKW = it;
				//			}
				//		}
				//		vec.push_back(make_pair(tmpKW->first, tmpKW->second));
				//		kw.erase(tmpKW);
				//	}
			}
			else {
				sort(vec.begin(), vec.end(), cmp);
			}
			for (int i = 0; i < vec.size() && i < rankNum; i++) {
				printf("%s:\t%d\n", vec[i].first.c_str(), vec[i].second);
			}
			kw.clear();
			vec.clear();
			printf("----------------------------\n");
		}
		fclose(fp);
	}
	return true;
}
/*
 *指令解析函数
 *@param: argc    参数个数
 *@param: *argv[] 参数列表
 *@return: 参数解析正常返回true,否则返回false
 */
bool readCommand(int argc, char *argv[]) {
	if (argc <= 1) {
		std::cout << ERROR1 << "\n";
		return false;
	}
	else if (argc == 2 && strcmp(argv[1], "--help") == 0) {
		help();
		return false;
	}
	int rankNum = INF, phraseNum = 1;
	bool isdigui = false, isall = false;
	string stopfilePath, verbfilePath, filePath, dirPath;

	for (int i = 1; i < argc; i++) {
		if (strlen(argv[i]) != 2 || argv[i][0] != '-') {
			std::cout << ERROR2 << "\n";
			return false;
		}
		int flg = 0;
		for (int j = 0; j < sizeof(paramter_list) / sizeof(paramter_list[0]); j++) {
			if (paramter_list[j][0] == argv[i][1]) {
				if (argv[i][1] == 'n' && i + 1 < argc) {
					rankNum = to_digit(argv[++i]);
					if (rankNum == -1) {
						std::cout << ERROR2 << "\n";
						return false;
					}
				}
				else if (argv[i][1] == 'p' && i + 1 < argc) {
					phraseNum = to_digit(argv[++i]);
					if (phraseNum == -1) {
						std::cout << ERROR2 << "\n";
						return false;
					}
				}
				else if (argv[i][1] == 'x' && i + 1 < argc) {
					stopfilePath = argv[++i];
				}
				else if (argv[i][1] == 'v' && i + 1 < argc) {
					verbfilePath = argv[++i];
				}
				else if (argv[i][1] == 'h' && argc == 2) {
					help();
					return false;
				}
				else if (argv[i][1] == 'f' && i + 1 < argc) {
					filePath = argv[++i];
				}
				else if (argv[i][1] == 'd' && i + 1 < argc) {
					dirPath = argv[++i];
				}
				else if (argv[i][1] == 's' && i < argc) {
					isdigui = true;
				}
				else if (argv[i][1] == 'a' && i < argc) {
					isall = true;
				}
				flg = 1;
				break;
			}
		}
		if (!flg) {
			std::cout << ERROR3 << "\n";
			return false;
		}
	}
	if (!filePath.empty() && !dirPath.empty()) {
		std::cout << ERROR2 << "\n";
	}
	else if (!filePath.empty()) {
		return sol(filePath, stopfilePath, verbfilePath, rankNum, phraseNum, isdigui, isall, false);
	}
	else if (!dirPath.empty()) {
		return sol(dirPath, stopfilePath, verbfilePath, rankNum, phraseNum, isdigui, isall, true);
	}
	else {
		std::cout << ERROR5 << "\n";
	}
	return false;
}
int main(int argc, char *argv[]) {
	clock_t start, end;
	start = clock(); //开始计时
	bool res = readCommand(argc, argv);
	end = clock();   //结束计时
	if (res)
		printf("total waste time: %lf\n", (double)(end - start) / CLOCKS_PER_SEC);
	//		cout << "程序总共用时: " << (double)(end - start) / CLOCKS_PER_SEC << "s" << "\n";
	return 0;
}
// 运行程序: Ctrl + F5 或调试 >“开始执行(不调试)”菜单
// 调试程序: F5 或调试 >“开始调试”菜单

// 入门使用技巧: 
//   1. 使用解决方案资源管理器窗口添加/管理文件
//   2. 使用团队资源管理器窗口连接到源代码管理
//   3. 使用输出窗口查看生成输出和其他消息
//   4. 使用错误列表窗口查看错误
//   5. 转到“项目”>“添加新项”以创建新的代码文件，或转到“项目”>“添加现有项”以将现有代码文件添加到项目
//   6. 将来，若要再次打开此项目，请转到“文件”>“打开”>“项目”并选择 .sln 文件
